//package emu;
package emu;

import emu.decvices.apu.APU;
import emu.decvices.bus.BUS;
import emu.decvices.bus.IRQLine;
import emu.decvices.cartridge.Cartridge;
import emu.decvices.clock.Clock;
import emu.decvices.cpu.CPU;
import emu.decvices.cpu.RAM;
import emu.decvices.joy.JoyPad;
import emu.decvices.ppu.PPU;
import emu.decvices.ppu.Palette;
import emu.decvices.ppu.TVScreen;
import emu.decvices.ppu.VRAM;
import emu.decvices.save.Save;
import emu.decvices.save.SaveHandler;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.net.URL;
import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.filechooser.FileNameExtensionFilter;

public class EMU {
    private static EMU emu;
    //窗口相关信息
    //窗口大小
    public static final int DEFAULT_SCREEN_WIDTH = 1000;
    public static final int DEFAULT_SCREEN_HEIGHT = 800;
    public static final int MENU_BAR_HEIGHT = 58;

    public static final File DEFAULT_ROM_PATH = new File("roms");
    public static final File DEFAULT_SAVE_PATH = new File("saves");
    public static final File DEFAULT_QUICK_SAVE_PATH = new File("quick_saves");

    private static final int DEFAULT_SCALE = 2;//屏幕成比例放大几倍
    private int scale = DEFAULT_SCALE;

    private JFrame frame;//游戏窗口


    //主机硬件相关信息
    private BUS cpuBus;//CPU总线
    private BUS ppuBus;//PPU总线

    private CPU cpu;//6502CPU
    private RAM cpu_ram;//连接在CPU上的RAM

    private PPU ppu;//2c02PPU

    private APU apu; 

    private Cartridge cartridge;//游戏卡带

    private Clock clock;//时钟

    private TVScreen tvScreen;//电视机

    private JoyPad joyPad1;//1p手柄
    private JoyPad joyPad2;//2p手柄

    private File currentRomFile;//当前选择的rom文件

    public EMU() {
        initDir();
        initFrame();
    }

    public void initDir() {
        DEFAULT_ROM_PATH.mkdirs();
        DEFAULT_SAVE_PATH.mkdirs();
        DEFAULT_QUICK_SAVE_PATH.mkdirs();
    }

    private void initFrame() {
        frame = new JFrame();
        // 设置窗口的大小
        frame.setSize(DEFAULT_SCREEN_WIDTH * scale, DEFAULT_SCREEN_HEIGHT * scale + MENU_BAR_HEIGHT);
        initMenu();//添加菜单
        tvScreen = new TVScreen(scale, DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT);
        frame.add(tvScreen);
        //frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //frame.setResizable(false);
        //frame.setFocusable(true);

        // 设置窗口的可见性
        frame.setVisible(true);
    }

    /**
     * 构建硬件之间的连接关系
     */
    private void createEMU() {
        //重置CPU中断线
        IRQLine.getInstance().reset();
        //初始化CPU相关
        cpu = new CPU();
        cpuBus = new BUS();
        cpu_ram = new RAM();
        cpuBus.linkDevice(cpu_ram);
        cpuBus.linkDevice(cpu);

        //初始化APU相关
        apu = new APU(cpu);
        cpuBus.linkDevice(apu);

        //初始化PPU相关
        ppu = new PPU();
        ppuBus = new BUS();
        ppuBus.linkDevice(ppu);
        //调色盘RAM
        Palette palette = new Palette();
        //将调色RAM器连接到PPU总线上
        ppuBus.linkDevice(palette);
        //VRAM
        VRAM vram = new VRAM();
        //将VRAM连接到PPU总线上
        ppuBus.linkDevice(vram);

        cpuBus.linkDevice(ppu.getRegister());//将PPU的寄存器连接到CPU总线，让CPU可以访问PPU寄存器0x2000-0x2007

        cpuBus.linkDevice(apu.getDma());
        cpuBus.linkDevice(ppu.getDma());


        ppu.setTvScreen(tvScreen);//将电视机连接到PPU上

        insertJoy();//添加手柄
        //时钟
        clock = new Clock(cpu, ppu, apu);
    }

    public void onClockShutdown() {
        if (needQuickSave) {
            quickSave();
        } else if (needLoadQuickSave) {
            quickLoad();
        } else {
            reset();
        }
    }

    public void reset() {
        try {
            createEMU();
            insertCard();
            reStart();
        } catch (Exception e) {
            clock = null;
            tvScreen.reset(scale, DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT);
            frame.repaint();
            JOptionPane.showMessageDialog(frame, e.getMessage());
            e.printStackTrace();
        }

    }

    public static EMU getInstance() {
        if (emu == null) {
            emu = new EMU();
        }
        return emu;
    }

    public static void main(String[] args) {
        getInstance();
    }

    public void insertJoy() {
        joyPad1 = new JoyPad(JoyPad.JOY_1P);
        cpuBus.linkDevice(joyPad1);
        frame.addKeyListener(joyPad1.getKeyAdapter());
        joyPad2 = new JoyPad(JoyPad.JOY_2P);
        cpuBus.linkDevice(joyPad2);
        frame.addKeyListener(joyPad2.getKeyAdapter());
    }

    /**
     * 开机
     */
    public void reStart() {
        if (cartridge == null) {
            System.out.println("没有插入卡带");
        } else {
            cpu.reset();
            clock.start();
        }
    }

    public void insertCard() {
        cartridge = new Cartridge(currentRomFile);
        //卡带拥有总线最优先读写权限
        cpuBus.linkDeviceFirst(cartridge.getMapper().getCpuDevice());
        ppuBus.linkDeviceFirst(cartridge.getMapper().getPpuDevice());
        ppuBus.linkDeviceFirst(cartridge.getMapper().getPpuVRamMapper());

        ppu.setMapper(cartridge.getMapper());
    }

    private void initMenu() {
        JMenuBar bar = new JMenuBar();
        //创建菜单
        JMenu startMenu = new JMenu("开始");
        initStartMenu(startMenu);

        JMenu joyMenu = new JMenu("手柄");
        initJoySetDialog(joyMenu);

        JMenu quickSaveMenu = new JMenu("存档");
        initQuickMenu(quickSaveMenu);

        JMenu settingMenu = new JMenu("设置");
        initSettingMenu(settingMenu);

        bar.add(startMenu);
        bar.add(joyMenu);
        bar.add(quickSaveMenu);
        bar.add(settingMenu);
        frame.setJMenuBar(bar);
    }
    //---------------------------------------------设置菜单初始化--------------------------------------------------
    private void initSettingMenu(JMenu menu) {
        initFrameResizeMenu(menu);
    }

    private void initFrameResizeMenu(JMenu menu){
        JMenuItem item = new JMenuItem("修改窗口大小");
        item.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JPopupMenu popupMenu = new JPopupMenu();
                for(int i=1;i<=4;i++){//最多放大4倍，如果有必要可以调整  4倍时窗口宽度已经为1024了
                    addMenuItem(popupMenu,i);
                }
                popupMenu.show(menu, 0, menu.getHeight());
            }
            private void addMenuItem(JPopupMenu popupMenu,int num){
                JMenuItem menuItem = new JMenuItem(num+"倍"+(scale==num?" √":"  "));
                menuItem.addActionListener(new AbstractAction() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        resizeFrame(num);
                        popupMenu.setVisible(false);
                    }
                });
                popupMenu.add(menuItem);
            }

            private void resizeFrame(int scale){
                EMU.this.scale = scale;
                frame.setSize(DEFAULT_SCREEN_WIDTH*scale,DEFAULT_SCREEN_HEIGHT*scale+MENU_BAR_HEIGHT);
                tvScreen.reset(scale,DEFAULT_SCREEN_WIDTH,DEFAULT_SCREEN_HEIGHT);
            }
        });
        menu.add(item);
    }

    //---------------------------------------------开始菜单初始化--------------------------------------------------
    private void initStartMenu(JMenu menu) {
        //在File中添加子菜单
        JMenuItem item1 = new JMenuItem("添加rom");
        item1.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JFileChooser chooser = new JFileChooser();
                chooser.setCurrentDirectory(DEFAULT_ROM_PATH);
                FileNameExtensionFilter filter = new FileNameExtensionFilter("rom文件", "nes");
                chooser.setFileFilter(filter);
                int returnValue = chooser.showOpenDialog(null);
                if (returnValue == JFileChooser.APPROVE_OPTION) {
                    currentRomFile = chooser.getSelectedFile();
                    frame.setTitle(currentRomFile.getName());
                    if (clock == null) {//clock为NULL说明第一次运行，emu没有初始化过
                        reset();
                    } else {
                        clockShutdown();//停止之前的时钟，重置游戏

                    }
                }
            }
        });
        JMenuItem item2 = new JMenuItem("复位");
        item2.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
               if(clock!=null){
                   clockShutdown();//停止之前的时钟，重置游戏
               }
            }
        });

        JMenuItem item3 = new JMenuItem("退出");
        item3.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });
        menu.add(item1);
        menu.add(item2);
        menu.add(item3);
    }


    //---------------------------------------------手柄设置相关菜单初始化--------------------------------------------------
    private void initJoySetDialog(JMenu menu) {

        JMenuItem item1 = new JMenuItem("修改1P按键");
        item1.addActionListener(e -> showSetButtonDialog(item1.getText(), JoyPad.JOY_1P));

        JMenuItem item2 = new JMenuItem("修改2P按键");
        item2.addActionListener(e -> showSetButtonDialog(item2.getText(), JoyPad.JOY_2P));

        menu.add(item1);
        menu.add(item2);
    }

    private void showSetButtonDialog(String title, int joyPadIndex) {
        // 创建一个新的窗口
        JFrame frame = new JFrame();
        frame.setTitle(title);

        // 加载图片
        URL url = getClass().getClassLoader().getResource("images/joypad.png");
        ImageIcon imageIcon = new ImageIcon(new ImageIcon(url).getImage().getScaledInstance(400, 200, Image.SCALE_DEFAULT));

        // 创建一个标签并将图片添加到标签
        JLabel label = new JLabel(imageIcon);
        label.setBounds(200, 150, imageIcon.getIconWidth(), imageIcon.getIconHeight()); // 设置标签的位置和大小

        //设置右键
        frame.add(createSetButton(165, 145, joyPadIndex, JoyPad.JOY_RIGHT));
        //设置上键
        frame.add(createSetButton(165, 185, joyPadIndex, JoyPad.JOY_UP));
        //设置左键
        frame.add(createSetButton(165, 250, joyPadIndex, JoyPad.JOY_LEFT));
        //设置下键
        frame.add(createSetButton(165, 305, joyPadIndex, JoyPad.JOY_DOWN));
        //设置y键
        frame.add(createSetButton(615, 145, joyPadIndex, JoyPad.JOY_Y));
        //设置x键
        frame.add(createSetButton(615, 185, joyPadIndex, JoyPad.JOY_X));
        //设置a键
        frame.add(createSetButton(615, 250, joyPadIndex, JoyPad.JOY_A));
        //设置b键
        frame.add(createSetButton(615, 305, joyPadIndex, JoyPad.JOY_B));
        //设置select键
        frame.add(createSetButton(358, 355, joyPadIndex, JoyPad.JOY_SELECT));
        //设置start键
        frame.add(createSetButton(406, 355, joyPadIndex, JoyPad.JOY_START));

        // 将标签添加到窗口
        frame.setLayout(null); // 使用绝对布局
        frame.add(label);

        // 设置窗口的其他属性
        frame.setSize(800, 600);
        frame.getContentPane().setBackground(Color.WHITE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private JTextField createSetButton(int x, int y, int joyPadIndex, int joyPadButtonIndex) {
        JTextField field = new JTextField();
        field.setFont(new Font("Arial", Font.PLAIN, 20));
        field.setBounds(x, y, 30, 30); // 设置输入框的位置和大小
        field.setEditable(false);
        field.setBorder(new LineBorder(Color.GRAY));
        field.setText(" " + KeyEvent.getKeyText(JoyPad.getButton(joyPadIndex, joyPadButtonIndex)));
        switch (JoyPad.getButton(joyPadIndex, joyPadButtonIndex)) {
            case KeyEvent.VK_UP:
                field.setText(" ↑");
                break;
            case KeyEvent.VK_DOWN:
                field.setText(" ↓");
                break;
            case KeyEvent.VK_LEFT:
                field.setText(" ←");
                break;
            case KeyEvent.VK_RIGHT:
                field.setText(" →");
                break;
        }
        field.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                field.setBorder(new LineBorder(Color.RED));
            }

            @Override
            public void focusLost(FocusEvent e) {
                field.setBorder(new LineBorder(Color.GRAY));
            }
        });
        field.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                // 获取按下的键并保存到数据库
                char key = e.getKeyChar();
                field.setText((" " + key).toUpperCase());
                JoyPad.setButton(joyPadIndex, joyPadButtonIndex, e.getKeyCode());
            }
        });
        return field;
    }

    //---------------------------------------------即时菜单初始化--------------------------------------------------
    public void initQuickMenu(JMenu menu) {
        JMenuItem item1 = new JMenuItem("即时存储 <F5>");
        item1.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                needQuickSave();
            }
        });


        JMenuItem item2 = new JMenuItem("即时读档 <F6>");
        item2.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (SaveHandler.quickLoadEnable(currentRomFile.getName())) {
                    needQuickLoad();
                }
            }
        });
        //使用快捷键实现快速存档与读档
        frame.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switch (e.getKeyCode()){
                    case KeyEvent.VK_F5:
                        needQuickSave();
                        break;
                    case KeyEvent.VK_F6:
                        needQuickLoad();
                        break;
                }

            }
        });

        menu.add(item1);
        menu.add(item2);
    }

    private void clockShutdown() {
        apu.apuShutdown();
        clock.shutdown();
    }

    private boolean needQuickSave;//需要即时存档
    private boolean needLoadQuickSave;//需要即时读档

    /**
     * 需要快速存档
     */
    private void needQuickSave(){
        needQuickSave = true;
        clockShutdown();
    }

    /**
     * 需要快速读档
     */
    private void needQuickLoad(){
        needLoadQuickSave = true;
        clockShutdown();
    }

    public void quickSave() {
        Save save = new Save(cpuBus, ppuBus, cpu, cpu_ram, ppu, apu, cartridge, clock, joyPad1, joyPad2, currentRomFile);
        SaveHandler.quickSave(currentRomFile.getName(), save);
        clock.start();
        apu.start();
        needQuickSave = false;
    }

    public void quickLoad() {
        Save save = SaveHandler.quickLoad(currentRomFile.getName());
        if (save != null) {
            cpuBus = save.getCpuBus();
            ppuBus = save.getPpuBus();
            cpu_ram = save.getCpu_ram();
            ppu = save.getPpu();
            apu = save.getApu();
            cartridge = save.getCartridge();
            clock = save.getClock();
            joyPad1 = save.getJoyPad1();
            joyPad2 = save.getJoyPad2();
            currentRomFile = save.getCurrentRomFile();
            ppu.setTvScreen(tvScreen);
            frame.addKeyListener(joyPad1.getKeyAdapter());
            frame.addKeyListener(joyPad2.getKeyAdapter());
            clock.start();
            apu.start();
            IRQLine.setInstance(save.getLine());
        }
        needLoadQuickSave = false;
    }
}
